package gae.testjig.client;

import java.util.HashMap;

import gae.testjig.client.eventbus.ChannelMetaEvent;
import gae.testjig.client.eventbus.CommandReadyEvent;
import gae.testjig.client.eventbus.EchoEvent;
import gae.testjig.client.eventbus.InfoEvent;
import gae.testjig.client.eventbus.TestStatusEvent;

import com.google.gwt.appengine.channel.client.Channel;
import com.google.gwt.appengine.channel.client.ChannelFactory;
import com.google.gwt.appengine.channel.client.SocketError;
import com.google.gwt.appengine.channel.client.SocketListener;
import com.google.gwt.appengine.channel.client.ChannelFactory.ChannelCreatedCallback;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.web.bindery.event.shared.EventBus;

public class ChannelProvider {
	
	static private int COUNTER = 0;
	
	public interface ChannelMonitor {
//		public void statusMessage(String message);
		public void onMessage(String message);
		public void onConnectFailure(String errorMessage);
		public void onError(String errorMessage);
		public void onClose();
	}
	public interface ChannelReady {
		public void onChannelReady();
	}

	private GaeTestJigServiceAsync service;
	private ChannelMonitor monitor;
	private String clientId;
	private SocketListener socketListener = null;
	private HashMap<String, ClientChannelId> map = new HashMap<String, ClientChannelId>();
	
	public ChannelProvider(GaeTestJigServiceAsync service, final EventBus eventBus) {
		this.service = service;
		this.clientId = "" + System.currentTimeMillis() + "-" + Random.nextInt(10000);
		
		this.monitor = new ChannelMonitor() {
//			@Override
//			public void statusMessage(String message) {
//				eventBus.fireEvent(new ChannelMetaEvent(message));
//			}
			@Override
			public void onMessage(String message) {
//				eventBus.fireEvent(new ChannelMetaEvent(message));
				
				JSONValue val = JSONParser.parseStrict(message);
				JSONObject obj = val.isObject();
				if ( obj != null ) {
					if ( obj.containsKey("echo") ) {
						String echoMessage = obj.get("echo").isString().stringValue();
						String channelId = obj.get("channelId").isString().stringValue();
						eventBus.fireEventFromSource(new EchoEvent(echoMessage), lookupClientChannelId(channelId));
					} else if ( obj.containsKey("info") ) {
						String msg = obj.get("info").isString().stringValue();
						String channelId = obj.get("channelId").isString().stringValue();
						eventBus.fireEventFromSource(new InfoEvent(msg), lookupClientChannelId(channelId));
					} else if ( obj.containsKey("ready") ) {
						String msg = obj.get("ready").isString().stringValue();
						String channelId = obj.get("channelId").isString().stringValue();
						eventBus.fireEventFromSource(new CommandReadyEvent(msg), lookupClientChannelId(channelId));
					} else if ( obj.containsKey("invocationCount") ) {
						String channelId = obj.get("channelId").isString().stringValue();
						JSONNumber invocations = obj.get("invocationCount").isNumber();
						JSONNumber retries = obj.get("retryCount").isNumber();
						JSONNumber mismatches = obj.get("mismatches").isNumber();
						eventBus.fireEventFromSource(new TestStatusEvent(
							(int)(invocations.doubleValue()),
							(int)(retries.doubleValue()),
							(int)(mismatches.doubleValue())
						), lookupClientChannelId(channelId));
						eventBus.fireEventFromSource(new InfoEvent(message), lookupClientChannelId(channelId));
					}
				}
			}
			@Override
			public void onError(String errorMessage) {
				eventBus.fireEvent(new ChannelMetaEvent(errorMessage));
			}
			@Override
			public void onConnectFailure(String errorMessage) {
				eventBus.fireEvent(new ChannelMetaEvent(errorMessage));
			}
			@Override
			public void onClose() {
			}
		};
	}
	
	private ClientChannelId lookupClientChannelId(String channelId) {
		return this.map.get(channelId);
	}
	
	private void registerClientChannelId(ClientChannelId ccid) {
		this.map.put(ccid.getChannelId(), ccid);
	}
	
	public ClientChannel createClientChannel() {
		String channelId = "" + COUNTER;
		COUNTER++;
		return new ClientChannel(this.clientId, channelId);
	}
	
	public class ClientChannel {

		private ClientChannelId clientChannelId;
		
		public ClientChannel(String clientId, String channelId) {
			this.clientChannelId = ClientChannelId.create(clientId, channelId);
			registerClientChannelId(this.clientChannelId);
		}
		
		public ClientChannelId getClientChannelId() {
			return this.clientChannelId;
		}
		
		public void ensureOpenChannel(ChannelReady ready) {
			if ( socketListener == null ) {
				initiateConnection(ready);
			} else {
				ready.onChannelReady();
			}
		}
		
		private void initiateConnection(final ChannelReady ready) {
//			monitor.statusMessage("initiating a channel connection. (clientId="+this.clientId+")");
			service.initiateChannelOpen(this.clientChannelId, new AsyncCallback<String>() {
				@Override
				public void onFailure(Throwable caught) {
					monitor.onConnectFailure(caught.getMessage());
				}
				@Override
				public void onSuccess(String token) {
//					monitor.statusMessage("completing channel setup. (token="+token+")");
					ChannelFactory.createChannel(token, new ChannelCreatedCallback() {
						@Override
						public void onChannelCreated(Channel channel) {
							socketListener = new SocketListener() {
								@Override
								public void onOpen() {
//									monitor.statusMessage("channel established.");
									ready.onChannelReady();
								}
								@Override
								public void onMessage(String message) {
									monitor.onMessage(message);
								}
								@Override
								public void onError(SocketError error) {
									monitor.onError(error.getDescription());
								}
								@Override
								public void onClose() {
//									monitor.statusMessage("channel closed.");
									monitor.onClose();
								}
							};
							channel.open(socketListener);
						}
					});
				}
			});
			
		}
	
	}
	
}
